<%
'######################################################################
'## ab.xml.asp
'## -------------------------------------------------------------------
'## Feature     :   AspBox XML Document Class
'## Version     :   v1.0
'## Author      :   Lajox(lajox@19www.com)
'## Update Date :   2014/04/01 15:15
'## Description :   Read and write the XML documents
'######################################################################

Class Cls_AB_Xml
	Public Dom, Doc, IsOpen
	Private s_filePath, s_xsltPath, s_charset
	Private Sub Class_Initialize()
		s_filePath = ""
		s_xsltPath = ""
		s_charset = AB.CharSet
		IsOpen = False
		AB.Error(96) = "XML文件操作出错"
		AB.Error(97) = "对象不支持此属性或方法"
		AB.Error(98) = "未找到目标对象"
		AB.Error(99) = "保存XML文档出错"
	End Sub

	Private Sub Class_Terminate()
		If IsObject(Doc) Then Set Doc = Nothing
		If IsObject(Dom) Then Set Dom = Nothing
	End Sub

	'------------------------------------------------------------------------------------------
	'# AB.Xml.IsOpen 属性(只读)
	'# @syntax: AB.Xml.IsOpen
	'# @return: Boolean (布尔值) 返回真(True)则表示文件已成功打开，假(False)则未打开
	'# @dowhat: 查询是否已经打开存在的xml文件，此属性只读
	'# 			此属性可以查询是否已经打开存在的xml文件，返回真(True)则表示文件已成功打开，假(False)则未打开。
	'# 			此属性只有当用 AB.Xml.Open 方法成功打开存在的xml文件时才会为真
	'--DESC------------------------------------------------------------------------------------
	'# @param: 无参数
	'--DEMO------------------------------------------------------------------------------------
	'# AB.Use "Xml"
	'# Dim str:str = "<?xml version=""1.0"" encoding=""gbk""?><x><y>y.text</y><y>y2.text</y></x>"
	'# AB.Xml.Load str
	'# AB.C.Print AB.Xml.Doc.Name  '输出根节点的名称 x
	'------------------------------------------------------------------------------------------

	'------------------------------------------------------------------------------------------
	'# AB.Xml.Doc 属性(只读)
	'# @syntax: [Set object =]AB.Xml.Doc
	'# @return: Object (ASP对象) AspBox Node对象
	'# @dowhat: 获取AspBox XML节点对象的原始element对象，只读
	'# 			可以通过此属性获取当前AspBox XML对象的根节点，即当前Xml文件中的第一个节点对象
	'--DESC------------------------------------------------------------------------------------
	'# @param: 无参数
	'--DEMO------------------------------------------------------------------------------------
	'# AB.Use "Xml"
	'# Dim str:str = "<?xml version=""1.0"" encoding=""gbk""?><x><y>y.text</y><y>y2.text</y></x>"
	'# AB.Xml.Load str
	'# AB.C.Print AB.Xml.Doc.Name  '输出根节点的名称 x
	'------------------------------------------------------------------------------------------

	'------------------------------------------------------------------------------------------
	'# AB.Xml.Dom 属性(只读)
	'# @syntax: [Set object =]AB.Xml.Dom
	'# @return: Object (ASP对象) 原始XML element对象
	'# @dowhat: 获取AspBox XML节点对象的原始element对象，只读
	'# 			通过调用此属性可以获取AspBox XML节点对象的原始XML element对象（node节点），
	'# 			可以在此对象上使用所有的XML element和XML node相应的原始方法和属性。
	'# 			所有通过 AB.Xml.Find、AB.Xml.Select 或者 AB.Xml.Sel 方法获取的AspBox XML对象也可使用此属性
	'--DESC------------------------------------------------------------------------------------
	'# @param: 无参数
	'--DEMO------------------------------------------------------------------------------------
	'# AB.Use "Xml"
	'# AB.Xml.Load "<?xml version=""1.0"" encoding=""gbk""?><x><y>y.text</y><y>y2.text</y></x>"
	'# AB.C.PrintCn AB.Xml.Dom.GetElementsByTagName("y")(0).NodeName '用原始方法取第一个y节点的名称 y
	'# AB.C.PrintCn AB.Xml.Dom.GetElementsByTagName("y")(0).NodeValue '用原始方法取第一个y节点的值
	'# AB.C.PrintCn AB.Xml.Dom.GetElementsByTagName("y")(0).NodeType '用原始方法取第一个y节点的类型
	'------------------------------------------------------------------------------------------

	'------------------------------------------------------------------------------------------
	'# AB.Xml.NewDom 方法
	'# @syntax: [Set obj =] AB.Xml.NewDom()
	'# @return: Object (ASP对象) 原始 XMLDOM对象
	'# @dowhat: 创建XMLDOM对象
	'--DESC------------------------------------------------------------------------------------
	'# @param: none
	'--DEMO------------------------------------------------------------------------------------
	'# Dim XmlDom : Set XmlDom = AB.Xml.NewDom()
	'------------------------------------------------------------------------------------------

	Private Function NewDom()
		Dim o
		'Set o = CreateXMLParser()
		If AB.C.IsInstall("MSXML2.DOMDocument") Then
			Set o = Server.CreateObject("MSXML2.DOMDocument")
		ElseIf AB.C.IsInstall("Microsoft.XMLDOM") Then
			Set o = Server.CreateObject("Microsoft.XMLDOM")
		End If
		o.PreserveWhiteSpace = True
		o.Async = False
		o.SetProperty "SelectionLanguage", "XPath"
		Set NewDom = o
		Set o = Nothing
	End Function

	'------------------------------------------------------------------------------------------
	'# AB.Xml.CreateXMLParser 方法
	'# @syntax: [Set obj =] AB.Xml.CreateXMLParser()
	'# @return: Object (ASP对象) 原始 XMLDOM对象
	'# @dowhat: 创建一个尽可能高版本的XMLDOM
	'--DESC------------------------------------------------------------------------------------
	'# @param: none
	'--DEMO------------------------------------------------------------------------------------
	'# Dim XmlDom : Set XmlDom = AB.Xml.CreateXMLParser()
	'------------------------------------------------------------------------------------------

	Public Function CreateXMLParser()
		On Error Resume Next
		Set CreateXMLParser = Server.CreateObject("MSXML2.DOMDocument.4.0")
		If Err.Number<>0 Then
			Err.Clear
			Set CreateXMLParser = Server.CreateObject("MSXML2.DOMDocument.3.0")
			If Err.Number<>0 Then
				Err.Clear
				Set CreateXMLParser = Server.CreateObject("MSXML2.DOMDocument.2.6")
				If Err.Number<>0 Then
					Err.Clear
					Set CreateXMLParser = Server.CreateObject("MSXML2.DOMDocument")
					If Err.Number<>0 Then
						Err.Clear
						Set CreateXMLParser = Server.CreateObject("Microsoft.XMLDOM")
						If Err.Number<>0 Then
							Err.Clear
							Set CreateXMLParser = Nothing
						Else
							Exit Function
						End If
					Else
						Exit Function
					End If
				Else
					Exit Function
				End If
			Else
				Exit Function
			End If
		Else
			Exit Function
		End If
		On Error GoTo 0
	End Function

	'------------------------------------------------------------------------------------------
	'# AB.Xml.Open 方法
	'# @syntax: AB.Xml.Open filePath
	'# @return: Boolean (布尔值) 打开成功返回真(True)，打开失败返回假(False)
	'# @dowhat: 打开本地服务器存在的xml文件
	'# 调用此方法可以打开一个存在于本地服务器上的xml文件，成功打开后， AB.Xml.IsOpen 属性将被设置为真(True)。
	'--DESC------------------------------------------------------------------------------------
	'# @param: filePath String (字符串)
	'#  要打开文件的路径，可以是相对路径、站点绝对路径(以/开头)和硬盘绝对路径(以"盘符:\"开头)
	'--DEMO------------------------------------------------------------------------------------
	'# '加载Xml核心
	'# AB.Use "Xml"
	'# '打开xml文件
	'# AB.Xml.Open "xmldata/micrblog_catalog.xml"
	'# '文件路径也可以是绝对路径
	'# Dim result
	'# result = AB.Xml.Open("D:\web\xmldata\micrblog_catalog.xml")
	'# '如果打开成功，输出True，如果打开失败，输出False
	'# AB.C.Print result
	'------------------------------------------------------------------------------------------

	Public Function Open(byVal f)
		Open = False
		If AB.C.IsNul(f) Then Exit Function
		Set Dom = NewDom()
		f = absPath(f)
		Dom.load f
		s_filePath = f
		If Not IsErr Then
			Set Doc = NewNode(Dom.documentElement)
			Open = True
			IsOpen = True
		Else
			Set Dom = Nothing
		End If
	End Function

	Private Function absPath(ByVal p)
		If AB.C.IsNul(p) Then absPath = "" : Exit Function
		If Mid(p,2,1)<>":" Then p = Server.MapPath(p)
		absPath = p
	End Function

	'------------------------------------------------------------------------------------------
	'# AB.Xml.Load 方法
	'# @syntax: AB.Xml.Load string|[encoding>]url
	'# @return: 无返回值
	'# @dowhat: 从文本或者远程网址载入XML数据
	'# 调用此方法可以从文本或者远程网址载入XML数据，
	'# 如果是从远程网址载入数据需要以 "http://" 或者 "https://" 开头，AspBox会自动判断目标xml文件的编码。
	'# 如果要指定远程文件的编码，可以在网址前加上编码，如 "gbk>http://..."。
	'# 如果要载入本地文件的编码，可以在路径后加上编码，如 "/files/a.xml>gbk"。
	'--DESC------------------------------------------------------------------------------------
	'# @param: string String (字符串) 有完整XML结构的字符串
	'# @param: encoding(可选) String (字符串)
	'#  要载入的远程xml文件的编码，AspBox会自动判断目标文件的编码，所以此参数可以省略。
	'#  除非目标xml文件没有指定encoding的情况下才需要用此参数指定载入文件的编码
	'# @param: url String (字符串) 以 http:// 或者 https:// 开头的xml文件的地址
	'--DEMO------------------------------------------------------------------------------------
	'# '加载xml核心
	'# AB.Use "Xml"
	'# Dim str
	'# str = "<?xml version=""1.0"" encoding=""gbk""?><x><y>y.text</y><y>y2.text</y></x>"
	'# '从文本载入Xml数据
	'# AB.Xml.Load str
	'# '输出根节点的名称
	'# AB.C.PrintCn AB.Xml.Doc.Name  '输出 x
	'# '从网址载入
	'# AB.Xml.Load "http://cf.qq.com/web200905/inc/cf_wallpapers.xml"
	'# AB.C.PrintCn AB.Xml.Doc.Name  '输出 wallpaper
	'------------------------------------------------------------------------------------------

	Public Sub [Load](ByVal s)
		If AB.C.IsNul(s) Then Exit Sub
		On Error Resume Next
		Dim str
		If IsObject(s) Then 'e.g. AB.Xml.Load Request
			Set Dom = NewDom()
			Dom.load(s)
		ElseIf AB.C.Test(s,"^(file:///?)?([A-Za-z]\:[\/\\]{1,2})?[\s]*[^\<>]+") Then
			AB.Use "Fso"
			Dim f : Set f = AB.Fso
			s = Replace(s, "file:///", "")
			s = Replace(s, "file://", "")
			str = f.Read(s)
			Set f = Nothing
			If AB.C.IsFile(s) Then IsOpen = True
			Set Dom = NewDom()
			Dom.loadXML(str)
		ElseIf AB.C.Test(s,"^([\w\d-]+>)?https?://") Then
			AB.Use "Http"
			Dim h : Set h = AB.Http.New
			str = h.Get(s)
			Set h = Nothing
			Set Dom = NewDom()
			Dom.loadXML(str)
		Else
			str = s
			Set Dom = NewDom()
			Dom.loadXML(str)
		End If
		If Not IsErr Then
			Set Doc = NewNode(Dom.documentElement)
		Else
			Set Dom = Nothing
		End If
		On Error Goto 0
	End Sub

	'直接创建根节点的 Xml Dom对象
	Public Function RootDom(ByVal root)
		Set Dom = NewDom()
		If AB.C.Has(root) Then
			'创建一个节点对象
			Dom.appendChild(Dom.CreateElement(root))
			'添加xml头部
			Dim head
			Set head = Dom.CreateProcessingInstruction("xml","version=""1.0"" encoding=""" & s_Charset & """")
			Dom.insertBefore head, Dom.childNodes(0)
		End If
		Set RootDom = Dom
	End Function

	'XML转Dict数据
	'e.g.
	' AB.Use "Xml" : AB.Xml.Load "/App/Lang/admin/zh-cn.xml"
	' AB.Trace AB.XML.XmlDict("config")
	Function XmlDict(ByVal node)
		On Error Resume Next
		Dim p, str, dict, o, t, i, j, k
		Dim oXml : Set oXml = Me
		p = node : k = node
		If Trim(p)="" Then
			p = oXml.Doc.Name '根节点的名称
			Set dict = AB.C.Dict()
			Set dict(p) = XmlDict(p)
			Set XmlDict = dict
			Exit Function
		End If
		If oXml(p).Length>1 Then '含有子节点
			Set dict = AB.C.Dict()
			For i=0 To oXml(p)(0).Length-1
				If oXml(p)(0).Child(i).IsNode Then
					If oXml(p)(0).Child(i).Length>1 Then '再包含子节点
						Set t = oXml(p)(0).Child(i)
						Set o = AB.C.Dict()
						k = k & ">" & oXml(p)(0).Child(i).Name
						For j=0 To t(0).Length-1
							If t(0).Child(j).IsNode Then
								If oXml(p)(0).Child(j).Length>1 Then '再包含子节点
									name = t(0).Child(j).Name
									Set o(name) = XmlDict(k)
								ElseIf oXml(p)(0).Child(j).Length=1 Then
									name = t(0).Child(j).Name
									text = t(0).Child(j).Text
									o(name) = text
								End If
							End If
						Next
						Set dict(t.Name) = o
						Set o = Nothing
						Set t = Nothing
					ElseIf oXml(p)(0).Child(i).Length=1 Then
						name = oXml(p)(0).Child(i).Name
						text = oXml(p)(0).Child(i).Text
						dict(name) = text
					End If
				End If
			Next
			Set str = dict
		ElseIf oXml(p).Length=1 Then '不含子节点
			str = oXml(p)(0).Text
		End If
		If IsObject(str) Then Set XmlDict = str Else XmlDict = str
		On Error Goto 0
	End Function

	'------------------------------------------------------------------------------------------
	'# AB.Xml.Close 方法
	'# @syntax: AB.Xml.Close
	'# @return: 无返回值
	'# @dowhat: 关闭打开的Xml文件
	'# 调用此方法可以关闭用 AB.Xml.Open 方法打开的xml文件并释放文档对象，同时 AB.Xml.IsOpen 的值也被设置为假(False)。
	'--DESC------------------------------------------------------------------------------------
	'# @param: none
	'--DEMO------------------------------------------------------------------------------------
	'# none
	'------------------------------------------------------------------------------------------

	Public Sub Close()
		Set Doc = Nothing
		Set Dom = Nothing
		s_filePath = ""
		IsOpen = False
	End Sub

	'------------------------------------------------------------------------------------------
	'# AB.Xml.Save 方法
	'# @syntax: AB.Xml.Save
	'# @return: 无返回值
	'# @dowhat: 保存已打开的修改后的xml文件
	'# 调用此方法可以保存用 AB.Xml.Open 方法打开之后并修改过的本地服务器上的xml文件
	'--DESC------------------------------------------------------------------------------------
	'# @param: none
	'--DEMO------------------------------------------------------------------------------------
	'# none
	'------------------------------------------------------------------------------------------

	Public Sub [Save]()
		If IsOpen Then
			Dom.Save(s_filePath)
		Else
			AB.Error.Msg = "（文档未处于打开状态）"
			AB.Error.Raise 99
		End If
	End Sub

	'------------------------------------------------------------------------------------------
	'# AB.Xml.SaveAs 方法
	'# @syntax: AB.Xml.SaveAs filePath[>encoding]
	'# @return: 无返回值
	'# @dowhat: 把当前的XML数据保存为一个xml文件
	'# 调用此方法可以把当前正在处理的XML对象的数据保存为一个文件。
	'# 可以在保存的文件名后加如 ">gbk" 来指定保存的编码，
	'# 如果不指定则：有编码申明的不改变，无编码申明的采用 默认值 AB.Charset 作编码。
	'# 用 AB.Xml.Load 方法载入的Xml数据只能用此方法保存文件。
	'--DESC------------------------------------------------------------------------------------
	'# @param: none
	'--DEMO------------------------------------------------------------------------------------
	'# AB.XML.Load("<?xml version=""1.0"" encoding=""gbk""?><root><x><y>y.text</y></x></root>")
	'# AB.Xml.SaveAs("E:/ASPWeb/wwwroot/mytest.xml")
	'------------------------------------------------------------------------------------------

	Public Sub SaveAs(ByVal p)
		Dim ch,cha,pi
		If Instr(p,">")>0 Then
			ch = AB.C.CRight(p,">")
			p = AB.C.CLeft(p,">")
		End If
		cha = AB.C.IfHas(ch, s_charset)
		p = absPath(p)
		Set pi = Dom.CreateProcessingInstruction("xml", "version=""1.0"" encoding=""" & cha & """")
		If Dom.FirstChild.BaseName<>"xml" Then
			Dom.InsertBefore pi, Dom.FirstChild
		Else
			If AB.C.Has(ch) Then Dom.ReplaceChild pi, Dom.FirstChild
		End If
		Dom.Save(p)
		Set pi = Nothing
	End Sub

	'------------------------------------------------------------------------------------------
	'# AB.Xml.SaveAsXHTML 方法
	'# @syntax: AB.Xml.SaveAsXHTML filePath, xslPath
	'# @return: 无返回值
	'# @dowhat: 用XSLT将当前XML数据保存为XHTML文档
	'# 调用此方法可以用XSLT文档将当前XML数据转换为XHTML文档并保存
	'--DESC------------------------------------------------------------------------------------
	'# @param filePath: 保存的XHTML文档文件路径
	'# @param xslPath: XSLT文档地址（XSLT一种用于转换 XML 文档的语言）
	'# xsl参考 http://www.w3.org/TR/WD-xsl http://www.w3.org/TR/xsl11/ http://www.w3.org/1999/XSL/Transform
	'--DEMO------------------------------------------------------------------------------------
	'# none
	'------------------------------------------------------------------------------------------

	Public Sub SaveAsXHTML(ByVal p, ByVal xsl)
		Dim x,f : Set x = [New]
		If AB.C.Test(xsl,"^([\w\d-]+>)?https?://") Then
			x.Load xsl
		Else
			x.Open xsl
		End If
		f = Dom.TransformNode(x.Dom)
		AB.Use "Fso"
		AB.Fso.CreateFile p, f
		Set x = Nothing
	End Sub

	'------------------------------------------------------------------------------------------
	'# AB.Xml.NewNode 方法
	'# @syntax: Set object = AB.Xml.NewNode(element)
	'# @return: Object (ASP对象) AspBox Node对象
	'# @dowhat: 用原始element对象建立AspBox Node对象
	'# 调用此方法可以用原始XML element和XML selection对象建立AspBox Node对象，之后就可以在该对象上应用AspBox Node的相关属性和方法
	'--DESC------------------------------------------------------------------------------------
	'# @param: 无参数
	'--DEMO------------------------------------------------------------------------------------
	'# none
	'------------------------------------------------------------------------------------------

	Public Function NewNode(ByVal o)
		Set NewNode = New Cls_AB_Xml_Node
		NewNode.Dom = o
	End Function

	'------------------------------------------------------------------------------------------
	'# AB.Xml.Root 属性(只读)
	'# @syntax: [Set object =]AB.Xml.Root
	'# @alias:  [Set object =]abNode.Root
	'# @return: Object (ASP对象) AspBox Node 对象
	'# @dowhat: 可以通过此属性获取当前XML数据的根文档对象(#document)
	'--DESC------------------------------------------------------------------------------------
	'# @param: none
	'--DEMO------------------------------------------------------------------------------------
	'# none
	'------------------------------------------------------------------------------------------

	Public Property Get Root
		Set Root = NewNode(Dom)
	End Property

	'------------------------------------------------------------------------------------------
	'# AB.Xml.New 方法
	'# @syntax: Set object = AB.Xml.New
	'# @return: Object (ASP对象) AspBox XML对象
	'# @dowhat: 创建新的AspBox XML对象
	'# 可以调用此方法创建新的AspBox XML对象，之后就可以在该对象上使用AspBox XML类的所有方法和属性
	'--DESC------------------------------------------------------------------------------------
	'# @param: 无参数
	'--DEMO------------------------------------------------------------------------------------
	'# '载入Xml核心
	'# AB.Use "Xml"
	'# Dim xml
	'# '创建新AspBox XML对象
	'# Set xml = AB.Xml.New
	'# xml.Open "xmldata/miroblog_catalog.xml"
	'# '......
	'# Set xml = Nothing
	'------------------------------------------------------------------------------------------

	Public Function [New]()
		Set [New] = New Cls_AB_Xml
	End Function

	'------------------------------------------------------------------------------------------
	'# AB.Xml.Find 方法
	'# @syntax: [Set object = ]AB.Xml.Find(selector)
	'# @alias:  [Set object = ]AB.Xml(selector)
	'# @return: Object (ASP对象) AspBox Node对象
	'# @dowhat: 选择Xml节点对象
	'# 			此方法是AspBox XML对象的默认方法，可以通过多种方式来选择xml数据中的节点，
	'# 			其使用方法类似于jQuery中的选择器，支持按标签名、属性、层级及部分XPath函数。
	'# 			另外，所有的AspBox Node对象也可用此方法在其内部选择子节点。
	'# 			此方法在AspBox XML类中可以省略Find，直接用 AB.Xml(selector) 进行选择；
	'# 			但AspBox Node对象使用此方法选择子节点时，不可以省略Find。
	'--DESC------------------------------------------------------------------------------------
	'# @param object(可选): Variable (变量) 已定义未初始化的变量
	'# @param selector: String (字符串) 用于选择节点的字符串，通常是标签名，也可以用以下符号：
	'#  "a,b" - 表示同时选择a、b两个节点
	'#  "a>b" - 表示在a的子节点中查找b
	'#  "a b" - 表示在a的后代节点中查找b
	'#  "a[b]" 或者 "a[@b]" - 表示查找带有属性b的a节点
	'#  "a[b='c']" 或者 "a[@b='c']" - 表示查找带有属性b的值为c的a节点
	'#  "a[b!='c']" 或者 "a[@b!='c']" - 表示查找带有属性b的值不为c的a节点
	'#  可以同时用多个属性定位节点，用[]分别包括即可，如 "a[b='c'][d='e'][f!='g']"
	'--DEMO------------------------------------------------------------------------------------
	'# '载入Xml核心
	'# AB.Use "Xml"
	'# '载入Xml数据，可点击下面的链接查看数据结构
	'# AB.Xml.Load "example/xml/microblog_catalog.xml"
	'# '选择所有标签为name的节点，并输出找到的节点个数
	'# AB.C.PrintCn AB.Xml("name").Length
	'# AB.C.PrintCn "--------"
	'# '选择所有包含属性alias的标签为name的节点，并输出找到的节点个数
	'# AB.C.PrintCn AB.Xml("name[alias]").Length
	'# AB.C.PrintCn "--------"
	'# '选择所有属性for等于me，nick属性不等于email的标签为account的节点，并输出其Xml代码
	'# AB.C.PrintCn AB.Xml("account[for='me'][nick!='email']").Xml
	'# AB.C.PrintCn "--------"
	'# '选择site节点的子节点中标签为name的节点，并输出其Xml代码
	'# AB.C.PrintCn AB.Xml("site>name").Xml
	'# AB.C.PrintCn "--------"
	'# '选择account节点的后代节点中标签为name的节点，并输出其Xml代码
	'# AB.C.PrintCn AB.Xml("account name").Xml
	'# AB.C.PrintCn "--------"
	'# '选择所有的url和last节点，并输出其Xml代码
	'# AB.C.PrintCn AB.Xml("url,last").Xml
	'# AB.C.PrintCn "--------"
	'------------------------------------------------------------------------------------------

	Public Default Function Find(ByVal t)
		Dim o,s
		If AB.C.Test(t,"^<[\s\S]+>$") Then
		Else
			If AB.C.Test(t, "[, >\[@:]") Then
				Set o = Dom.selectNodes(AB_Xml_TransToXpath(t))
			Else
				Set o = Dom.GetElementsByTagName(t)
			End If
		End If
		' If o.Length = 0 Then
			' AB.Error.Msg = "("&t&")"
			' AB.Error.Raise 98
		' End If
		If o.Length = 1 Then
			Set Find = NewNode(o(0))
		Else
			Set Find = NewNode(o)
		End If
	End Function

	'------------------------------------------------------------------------------------------
	'# AB.Xml.Length 方法
	'# @syntax: [number = ]AB.Xml.Length(selector)
	'# @alias:  [number = ]AB.Xml(selector).Length 或 [number = ]AB.Xml.Count(selector)
	'# @return: Integer (整型) 计算节点个数
	'# @dowhat: 获取节点个数
	'--DESC------------------------------------------------------------------------------------
	'# @param number(可选): Variable (变量) 已定义未初始化的变量
	'# @param selector: String (字符串) 用于选择节点的字符串，通常是标签名，也可以用以下符号：
	'#  "a,b" - 表示同时选择a、b两个节点
	'#  "a>b" - 表示在a的子节点中查找b
	'#  "a b" - 表示在a的后代节点中查找b
	'#  "a[b]" 或者 "a[@b]" - 表示查找带有属性b的a节点
	'#  "a[b='c']" 或者 "a[@b='c']" - 表示查找带有属性b的值为c的a节点
	'#  "a[b!='c']" 或者 "a[@b!='c']" - 表示查找带有属性b的值不为c的a节点
	'#  可以同时用多个属性定位节点，用[]分别包括即可，如 "a[b='c'][d='e'][f!='g']"
	'--DEMO------------------------------------------------------------------------------------
	'# AB.XML.Load("<?xml version=""1.0"" encoding=""gbk""?><root><x><y>y.text</y></x></root>")
	'# If AB.Xml("y").Length>0 Then
	'# 	AB.Trace AB.Xml("y")(0).Xml '输出：<y>y.text</y>
	'# 	AB.Trace AB.Xml("y")(0).Name '输出：y
	'# 	AB.Trace AB.Xml("y")(0).Value '输出：y.text
	'# End If
	'------------------------------------------------------------------------------------------

	Public Function Length(ByVal t)
		Length = Find(t).Length
	End Function
	Public Function Count(ByVal t) : Count = Find(t).Length : End Function

	'------------------------------------------------------------------------------------------
	'# AB.Xml.Select 方法
	'# @syntax: [Set object = ]AB.Xml.Select(xPath)
	'# @alias:  [Set object = ]AB.Xml.Sel(xPath)
	'# @return: Object (ASP对象) AspBox Node 对象
	'# @dowhat: 用XPath表达式选择节点
	'# 调用此方法可以用XPath表达式选择节点，相当于原始XML对象的 selectNodes 方法，不同的是返回的是 AspBox Node 对象
	'--DESC------------------------------------------------------------------------------------
	'# @param xPath: String(字符串) XPath表达式(一种用于在 XML 文档中导航的语言)
	'# 可以参考：http://www.w3school.com.cn/xpath/index.asp
	'--DEMO------------------------------------------------------------------------------------
	'# AB.XML.Load("<?xml version=""1.0"" encoding=""gbk""?><root><x><y>y.text</y></x></root>")
	'# AB.Trace AB.Xml.Select("/root/x/y")(0).Value
	'------------------------------------------------------------------------------------------

	Public Function [Select](ByVal p)
		Set [Select] = NewNode(Dom.selectNodes(p))
	End Function

	Public Function Sel(ByVal p)
		Set Sel = NewNode(Dom.selectSingleNode(p))
	End Function

	'------------------------------------------------------------------------------------------
	'# AB.Xml.Create 方法
	'# @syntax: Set object = AB.Xml.Create(name[tagType], value)
	'# @return: Object (ASP对象) AspBox Node 对象
	'# @dowhat: 新建一个AspBox Node 节点
	'# 调用此方法可以新建一个 AspBox Node 节点，可以通过不同的 tagType 参数创建普通节点、CDATA节点和Comment节点。
	'--DESC------------------------------------------------------------------------------------
	'# @param: none
	'--DEMO------------------------------------------------------------------------------------
	'# none
	'------------------------------------------------------------------------------------------

	Public Function Create(ByVal n, ByVal v)
		Dim o,p,cd
		If Instr(n," ")>0 Then
			cd = LCase(AB.C.CRight(n," "))
			n = AB.C.CLeft(n," ")
		End If
		If cd="comment" Then
			Set o = Dom.createComment(v)
		Else
			Set o = Dom.CreateElement(n)
			If cd = "cdata" Then
				Set p = Dom.CreateCDATASection(v)
			Else
				Set p = Dom.CreateTextNode(v)
			End If
			o.AppendChild(p)
		End If
		Set Create = NewNode(o)
		Set o = Nothing
		Set p = Nothing
	End Function

	Private Function IsErr()
		Dim s
		IsErr = False
		If Dom.ParseError.Errorcode<>0 Then
			With Dom.ParseError
				s = s & "	<ul class=""dev"">" & vbCrLf
				s = s & "		<li class=""info"">以下信息针对开发者：</li>" & vbCrLf
				s = s & "		<li>错误代码：0x" & Hex(.Errorcode) & "</li>" & vbCrLf
				If AB.C.Has(.Reason) Then s = s & "		<li>错误原因：" & .Reason & "</li>" & vbCrLf
				If AB.C.Has(.Url) Then s = s & "		<li>错误来源：" & .Url & "</li>" & vbCrLf
				If AB.C.Has(.Line) And .Line<>0 Then s = s & "		<li>错误行号：" & .Line & "</li>" & vbCrLf
				If AB.C.Has(.Filepos) And .Filepos<>0 Then s = s & "		<li>错误位置：" & .Filepos & "</li>" & vbCrLf
				If AB.C.Has(.SrcText) Then s = s & "		<li>源 文 本：" & .SrcText & "</li>" & vbCrLf
				s = s & "	</ul>" & vbCrLf
			End With
			IsErr = True
			AB.Error.Msg = s
			AB.Error.Raise 96
		End If
	End Function

End Class

Class Cls_AB_Xml_Node
	Private o_node

	Private Sub Class_Initialize()

	End Sub

	Private Sub Class_Terminate()
		Set o_node = Nothing
	End Sub

	Private Function [New](ByVal o)
		Set [New] = New Cls_AB_Xml_Node
		[New].Dom = o
	End Function

	Public Property Let Dom(ByVal o)
		If Not o Is Nothing Then
			Set o_node = o
		Else
			AB.Error.Msg = "(不是有效的XML对象)"
			AB.Error.Raise 97
		End If
	End Property

	Public Property Get Dom
		Set Dom = o_node
	End Property

	'------------------------------------------------------------------------------------------
	'# AB.Xml(selector).Item 属性(只读)
	'# @syntax: [Set object = ]abNode.Item(index)
	'# @alias:  [Set object = ]abNode.Item(index)
	'# @return: Object (ASP对象) AspBox Node 对象
	'# @dowhat: 选择AspBox Node对象集合中的某一个AspBox Node对象
	'# 			调用些方法可以获取 AspBox Node 对象集合中的某一个 AspBox Node 对象。
	'# 			如果对象本身就是一个AspBox Node对象而不是一个集合，则可以用为 0 的 index 参数返回自身。
	'--DESC------------------------------------------------------------------------------------
	'# @param: ###
	'--DEMO------------------------------------------------------------------------------------
	'# none
	'------------------------------------------------------------------------------------------

	Public Default Property Get Item(ByVal n)
		If IsNodes Then
			Set Item = [New](o_node(n))
		ElseIf IsNode And n = 0 Then
			Set Item = [New](o_node)
		Else
			AB.Error.Msg = "(不是有效的XML元素集合对象&lt;"&TypeName(o_node)&"&gt;)"
			AB.Error.Raise 97
		End If
	End Property

	'-------------------------------------------------------------------------
	' @ AB.Xml(selector).IsNode 查询当前AspBox Node对象是否是元素节点，只读
	'-------------------------------------------------------------------------
	' Desc:  可以用此属性查询当前AspBox Node对象是否是元素节点(element)
	'-------------------------------------------------------------------------

	Public Property Get IsNode
		IsNode = TypeName(o_node) = "IXMLDOMElement"
	End Property

	'-------------------------------------------------------------------------
	' @ AB.Xml(selector).IsNodes 查询当前AspBox Node对象是否是元素节点集合
	'-------------------------------------------------------------------------
	' Desc:  可以用此属性当前AspBox Node对象是否是元素节点集合(selection)
	'-------------------------------------------------------------------------

	Public Property Get IsNodes
		IsNodes = TypeName(o_node) = "IXMLDOMSelection"
	End Property

	'------------------------------------------------------------------------------------------
	'# AB.Xml(selector).Item 属性
	'# @syntax: abNode.Attr(name)[ = value]
	'# @return: String (字符串) 属性值
	'# @dowhat: 读取或设置当前AspBox Node元素节点的属性，全局参数，读写
	'# 			可以用此属性读取或设置XML元素节点的属性值，
	'# 			如果abNode参数是一个集合对象，则在设置值时会把集合内的所有节点的属性都设置为相同的值
	'--DESC------------------------------------------------------------------------------------
	'# @param: ###
	'--DEMO------------------------------------------------------------------------------------
	'# AB.Use "Xml"
	'# AB.Xml.Load "<?xml version=""1.0"" encoding=""gbk""?><x><y>y.text</y><y>y2.text</y></x>"
	'# dim abDoc : Set abDoc = AB.Xml.Doc
	'# abDoc.Attr("abcd") = "123"
	'# ab.c.print abDoc.name & "<br>" '输出: x<br>
	'# ab.c.print abDoc.Attr("abcd") '输出: 123
	'------------------------------------------------------------------------------------------

	Public Property Let Attr(ByVal s, ByVal v)
		If IsNull(v) Then RemoveAttr s : Exit Property
		If IsNode Then
			o_node.setAttribute s, v
		ElseIf IsNodes Then
			Dim i
			For i = 0 To Length - 1
				o_node(i).setAttribute s, v
			Next
		End If
	End Property

	Public Property Get Attr(ByVal s)
		If Not IsNode Then Exit Property
		Attr = o_node.getAttribute(s)
	End Property

	Public Property Let Text(ByVal v)
		If IsNode Then
			If AB.C.Has(v) Then o_node.Text = v
		ElseIf IsNodes Then
			Dim i
			For i = 0 To Length - 1
				If AB.C.Has(v) Then o_node(i).Text = v
			Next
		End If
	End Property

	'------------------------------------------------------------------------------------------
	'# AB.Xml(selector).Text 属性
	'# @syntax: abNode.Text [ = value]
	'# @return: String (字符串) 节点元素内的文本
	'# @dowhat: 读取和设置当前AspBox Node对象中元素节点的文本，此属性可读可写
	'# 使用此属性可以设置和获取AspBox Node对象的文本，如果是Node集合，则返回或者设置所有节点的文本
	'--DESC------------------------------------------------------------------------------------
	'# @param: 无参数
	'--DEMO------------------------------------------------------------------------------------
	'# none
	'------------------------------------------------------------------------------------------

	Public Property Get Text
		If IsNode Then
			Text = o_node.Text
		ElseIf IsNodes Then
			Dim i
			For i = 0 To Length - 1
				Text = Text & o_node(i).Text
			Next
		End If
	End Property

	'------------------------------------------------------------------------------------------
	'# AB.Xml(selector).Value 属性
	'# @syntax: abNode.Value[ = string]
	'# @return: String (字符串) 节点元素内的值
	'# @dowhat: 设置和获取当前AspBox Node对象元素节点的值，此属性可读可写
	'# 使用此属性可以设置和获取AspBox Node对象的值。如果是Node集合，则会设置所有节点的值，但无法获取值
	'--DESC------------------------------------------------------------------------------------
	'# @param: 无参数
	'--DEMO------------------------------------------------------------------------------------
	'# none
	'------------------------------------------------------------------------------------------

	Public Property Let Value(ByVal v)
		If IsNode Then
			o_node.ChildNodes(0).NodeValue = v
		ElseIf IsNodes Then
			Dim i
			For i = 0 To Length - 1
				o_node(i).ChildNodes(0).NodeValue = v
			Next
		End If
	End Property

	Public Property Get Value
		If Not IsNode Then Exit Property
		Value = o_node.ChildNodes(0).NodeValue
	End Property

	'------------------------------------------------------------------------------------------
	'# AB.Xml(selector).Xml 属性
	'# @syntax: abNode.Xml
	'# @return: String (字符串) XML代码片断
	'# @dowhat: 获取当前AspBox Node对象的Xml代码，此属性只读
	'# 			调用此属性可以返回当前AspBox Node对象元素节点的XML代码片段
	'--DESC------------------------------------------------------------------------------------
	'# @param: 无参数
	'--DEMO------------------------------------------------------------------------------------
	'# none
	'------------------------------------------------------------------------------------------

	Public Property Get Xml
		If IsNode Then
			Xml = o_node.Xml
		ElseIf IsNodes Then
			Dim i
			For i = 0 To Length - 1
				If i>0 Then Xml = Xml & vbCrLf
				Xml = Xml & o_node(i).Xml
			Next
		End If
	End Property

	Public Property Get Name
		If Not IsNode Then Exit Property
		Name = o_node.BaseName
	End Property

	Public Property Get [Type]
		If IsNodes Then
			[Type] = 0
		Else
			[Type] = o_node.NodeType
		End If
	End Property

	Public Property Get TypeString
	If IsNodes Then
			TypeString = "selection"
		Else
			TypeString = o_node.NodeTypeString
		End If
	End Property

	Public Property Get Length
		If IsNode Then
			Length = o_node.ChildNodes.Length
		Else
			Length = o_node.Length
		End If
	End Property

	Public Property Get Root
		If Not IsNode Then Exit Property
		Set Root = [New](o_node.OwnerDocument)
	End Property

	Public Property Get Parent
		If Not IsNode Then Exit Property
		Set Parent = [New](o_node.parentNode)
	End Property

	Public Property Get ChildNodes
		If Not IsNode Then Exit Property
		Set ChildNodes = [New](o_node.ChildNodes)
	End Property

	Public Property Get Child(ByVal n)
		If Not IsNode Then Exit Property
		Set Child = [New](o_node.ChildNodes(n))
	End Property

	Public Property Get Prev
		If Not IsNode Then Exit Property
		Dim o
		Set o = o_node.PreviousSibling
		Do While True
			If TypeName(o) = "Nothing" Or TypeName(o) = "IXMLDOMElement" Then Exit Do
			Set o = o.PreviousSibling
		Loop
		If TypeName(o) = "IXMLDOMElement" Then
			Set [Prev] = [New](o)
			Set o = Nothing
		Else
			AB.Error.Msg = "(没有上一同级元素)"
			AB.Error.Raise 96
		End If
	End Property

	Public Property Get [Next]
		If Not IsNode Then Exit Property
		Dim o
		Set o = o_node.NextSibling
		Do While True
			If TypeName(o) = "Nothing" Or TypeName(o) = "IXMLDOMElement" Then Exit Do
			Set o = o.NextSibling
		Loop
		If TypeName(o) = "IXMLDOMElement" Then
			Set [Next] = [New](o)
			Set o = Nothing
		Else
			AB.Error.Msg = "(没有下一同级元素)"
			AB.Error.Raise 96
		End If
	End Property

	Public Property Get First
		If Not IsNode Then Exit Property
		Set First = [New](o_node.FirstChild)
	End Property

	Public Property Get Last
		If Not IsNode Then Exit Property
		Set Last = [New](o_node.LastChild)
	End Property

	Public Function HasAttr(ByVal s)
		If Not IsNode Then Exit Function
		Dim oattr
		Set oattr = o_node.Attributes.GetNamedItem(s)
		HasAttr = Not oattr Is Nothing
		Set oattr = Nothing
	End Function

	Public Function HasChild()
		If Not IsNode Then Exit Function
		HasChild = o_node.hasChildNodes()
	End Function

	Public Function Find(ByVal t)
		If Not IsNode Then Exit Function
		Dim o
		If AB.C.Test(t, "[, >\[@:]") Then
			Set o = o_node.selectNodes(AB_Xml_TransToXpath(t))
		Else
			Set o = o_node.GetElementsByTagName(t)
		End If
		If o.Length = 0 Then
			'AB.Error.Msg = "("&t&")"
			'AB.Error.Raise 98
			Set Find = [New](o)
		ElseIf o.Length = 1 Then
			Set Find = [New](o(0))
		Else
			Set Find = [New](o)
		End If
	End Function

	Public Function [Select](ByVal p)
		If Not IsNode Then Exit Function
		Set [Select] = [New](o_node.selectNodes(p))
	End Function

	Public Function Sel(ByVal p)
		If Not IsNode Then Exit Function
		Set Sel = [New](o_node.selectSingleNode(p))
	End Function

	Public Function Clone(ByVal b)
		If Not IsNode Then Exit Function
		If AB.C.IsNul(b) Then b = True
		Set Clone = [New](o_node.CloneNode(b))
	End Function

	Private Function GetNodeDom(ByVal o)
		Select Case TypeName(o)
			Case "IXMLDOMElement" Set GetNodeDom = o
			Case "Cls_AB_Xml_Node" Set GetNodeDom = o.Dom
		End Select
	End Function

	Public Function Append(ByVal o)
		If Not IsNode Then Exit Function
		o_node.AppendChild(GetNodeDom(o))
		Set Append = [New](o_node)
	End Function

	Public Function ReplaceWith(ByVal o)
		If IsNode Then
			Call o_node.ParentNode.replaceChild(GetNodeDom(o), o_node)
		ElseIf IsNodes Then
			Dim i,n
			For i = 0 To Length - 1
				Set n = GetNodeDom(o).CloneNode(True)
				Call o_node(i).ParentNode.replaceChild(n, o_node(i))
			Next
		End If
		Set ReplaceWith = [New](o_node)
	End Function

	Public Function Before(ByVal o)
		If IsNode Then
			Call o_node.ParentNode.InsertBefore(GetNodeDom(o), o_node)
		ElseIf IsNodes Then
			Dim i,n
			For i = 0 To Length - 1
				Set n = GetNodeDom(o).CloneNode(True)
				Call o_node(i).ParentNode.InsertBefore(n, o_node(i))
			Next
		End If
		Set Before = [New](o_node)
	End Function

	Public Function After(ByVal o)
		If IsNode Then
			Call InsertAfter(GetNodeDom(o), o_node)
		ElseIf IsNodes Then
			Dim i,n
			For i = 0 To Length - 1
				Set n = GetNodeDom(o).CloneNode(True)
				Call InsertAfter(n, o_node(i))
			Next
		End If
		Set After = [New](o_node)
	End Function

	Private Sub InsertAfter(ByVal n, Byval o)
		Dim p
		Set p = o.ParentNode
		If p.LastChild Is o Then
			p.AppendChild(n)
		Else
			Call p.InsertBefore(n, o.nextSibling)
		End If
	End Sub

	Public Function RemoveAttr(ByVal s)
		If IsNode Then
			o_node.removeAttribute(s)
		ElseIf IsNodes Then
			Dim i
			For i = 0 To Length - 1
				o_node(i).removeAttribute(s)
			Next
		End If
		Set RemoveAttr = [New](o_node)
	End Function

	Public Function [Empty]
		If IsNode Then
			o_node.Text = ""
		ElseIf IsNodes Then
			Dim i
			For i = 0 To Length - 1
				o_node(i).Text = ""
			Next
		End If
		Set [Empty] = [New](o_node)
	End Function

	Public Function Clear
		If IsNode Then
			o_node.Text = ""
			o_node.removeChild(o_node.FirstChild)
		ElseIf IsNodes Then
			Dim i
			For i = 0 To Length - 1
				o_node(i).Text = ""
				o_node(i).removeChild(o_node(i).FirstChild)
			Next
		End If
		Set Clear = [New](o_node)
	End Function

	Public Function Normalize
		If IsNode Then
			o_node.normalize()
		ElseIf IsNodes Then
			Dim i
			For i = 0 To Length - 1
				o_node(i).normalize()
			Next
		End If
		Set Normalize = [New](o_node)
	End Function

	Public Sub Remove
		If IsNode Then
			o_node.ParentNode.RemoveChild(o_node)
		ElseIf IsNodes Then
			Dim i
			For i = 0 To Length - 1
				o_node(i).ParentNode.RemoveChild(o_node(i))
			Next
		End If
	End Sub

End Class
Function AB_Xml_TransToXpath(ByVal s)
	s = AB.C.RegReplace(s, "\s*,\s*", "|//")
	s = AB.C.RegReplace(s, "\s*>\s*", "/")
	s = AB.C.RegReplace(s, "\s+", "//")
	s = AB.C.RegReplace(s, "(\[)([a-zA-Z]+\])", "$1@$2")
	s = AB.C.RegReplace(s, "(\[)([a-zA-Z]+[!]?=[^\]]+\])", "$1@$2")
	s = AB.C.RegReplace(s, "(?!\[\d)\]\[", " and ")
	s = Replace(s, "|", " | ")
	AB_Xml_TransToXpath = "//" & s
End Function
%>